﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Input;
using Vortex.Drawing;
using Vortex.SceneFramework.Effects;

namespace Vortex.SceneFramework {

	/// <summary>
	/// Root of screen graph object tree.
	/// </summary>
	public sealed class ScreenManager : IDisposable {
		private IDrawingTarget _screenTarget;
		private IMouse _mouse;
		private IKeyboard _keyboard;
		private InputManager _inputDispatcher;
		private GraphSnapshot _graphSnapshot = new GraphSnapshot();
		///<summary>Stack of scenes</summary>
		private Stack<Scene> _sceneStack = new Stack<Scene>();
		private List<OverlayScene> _sceneList = new List<OverlayScene>();
		private List<OverlayScene> _modalSceneList = new List<OverlayScene>();

		//render targets used by screen manager
		private AutoTargetTexture _primaryTarget;
		private AutoTargetTexture _backupTarget;

		//current playing effect
		private SceneSwitchEffectPlayer _switchEffectPlayer;

		static ScreenManager() {
			//initialize style sheet properties
			Styles.Configure();
		}

		/// <summary>
		/// Initializes domain manager. Setups the screen, binds to input sources.
		/// </summary>
		/// <param name="mouse">The mouse interface.</param>
		/// <param name="keyboard">The keyboard interface.</param>
		public ScreenManager(IDrawingTarget screenTarget, IMouse mouse, IKeyboard keyboard) {
			_screenTarget = screenTarget;
			_mouse = mouse;
			_keyboard = keyboard;
			_inputDispatcher = new InputManager(this);

			//create two auto textures with using shared Z-Buffer
			_primaryTarget = new AutoTargetTexture(screenTarget, DrawingTargetZUsage.Shared);
			_backupTarget = new AutoTargetTexture(screenTarget, DrawingTargetZUsage.Shared);
		}

		#region IDisposable Members

		/// <summary>
		/// Cleans up all of unmanaged resources of screen manager
		/// </summary>
		public void Dispose() {
			_primaryTarget.Dispose();
			_backupTarget.Dispose();
		}

		#endregion

		/// <summary>
		/// Gets the domain manager mouse listener interface.
		/// </summary>
		/// <value>The mouse interface.</value>
		public IMouse Mouse {
			get { return _mouse; }
		}

		/// <summary>
		/// Gets the domain manager keyboard listener interface.
		/// </summary>
		/// <value>The keyboard interface.</value>
		public IKeyboard Keyboard {
			get { return _keyboard; }
		}

		#region Element Access

		/// <summary>
		/// Gets the visible elements on screen.
		/// </summary>
		/// <returns>Enumeration of all visible elements</returns>
		public IEnumerable<Node> GetVisibleElements() {
			Scene scene = CurrentScene;
			if (null != scene) yield return scene;

			//common overlays should be drawn after background domain
			for (int n = 0; n < _sceneList.Count; ++n) {
				yield return _sceneList[n];
			}

			//modals should be drawn over
			for (int n = 0; n < _modalSceneList.Count; ++n) {
				yield return _modalSceneList[n];
			}
		}

		/// <summary>
		/// Gets enumeration of elements which should receive input.
		/// </summary>
		/// <returns>Enumeration of all input target elements</returns>
		public IEnumerable<Node> GetInputTargetElements() {
			if (_modalSceneList.Count > 0) {
				yield return _modalSceneList[_modalSceneList.Count - 1];
			} else {
				//iterate over overlayed scenes
				for (int n = _sceneList.Count - 1; n >= 0; --n) {
					yield return _sceneList[n];
				}

				//lets return last domain
				Scene scene = CurrentScene;
				if (null != scene) yield return scene;
			}
		}

		#endregion

		#region Domain Managemnet

		/// <summary>
		/// Gets the current active background domain.
		/// </summary>
		/// <value>The current background domain.</value>
		public Scene CurrentScene {
			get {
				if (_sceneStack.Count > 0) {
					return _sceneStack.Peek();
				} else {
					return null;
				}
			}
			set { SetScene(value); }
		}

		/// <summary>
		/// Gets the size of the domain stack.
		/// </summary>
		/// <value>The size of the domain stack.</value>
		public int SceneStackSize {
			get { return _sceneStack.Count; }
		}

		/// <summary>
		/// Immediately sets the specified domain as current.
		/// </summary>
		/// <param name="domain">The current domain. May be null.</param>
		public void SetScene(Scene scene) {
			SetScene(scene, null, 0);
		}

		/// <summary>
		/// Switches the domain with specified domain switch effect.
		/// </summary>
		/// <param name="domain">The new domain.</param>
		/// <param name="switchEffect">The switch effect.</param>
		/// <param name="timeToSwitch">The time to switch.</param>
		public void SetScene(Scene scene, ISceneSwitchEffect switchEffect, float timeToSwitch) {
			if (null == scene) throw new ArgumentNullException("scene");
			if (null != switchEffect) {
				StartEffect(switchEffect, timeToSwitch);
			}

			if (_sceneStack.Count > 0) {
				_sceneStack.Pop().Deactivate();
			}

			//change domain
			_sceneStack.Push(scene);

			//invoke activate for new domain...
			if (null != scene) {
				scene.Activate();
			}
		}

		/// <summary>
		/// Switches the domain with specified domain switch effect placing it on top of domain stack
		/// </summary>
		/// <param name="domain">The new top domain.</param>
		public void PushScene(Scene scene) {
			PushScene(scene, null, 0);
		}

		/// <summary>
		/// Switches the domain with specified domain switch effect placing it on top of domain stack
		/// </summary>
		/// <param name="domain">The new top domain.</param>
		/// <param name="switchEffect">The switch effect.</param>
		/// <param name="timeToSwitch">The time to switch.</param>
		public void PushScene(Scene scene, ISceneSwitchEffect switchEffect, float timeToSwitch) {
			if (null == scene) throw new ArgumentNullException("scene");
			if (null != switchEffect) {
				StartEffect(switchEffect, timeToSwitch);
			}

			if (_sceneStack.Count > 0) {
				_sceneStack.Peek().Deactivate();
			}

			//change domain
			_sceneStack.Push(scene);

			//invoke activate for new domain...
			scene.Activate();
		}

		/// <summary>
		/// Pops the domain. Activating previous one removing top.
		/// </summary>
		/// <param name="switchEffect">The switch effect.</param>
		/// <param name="timeToSwitch">The time to switch.</param>
		public void PopScene() {
			PopScene(null, 0f);
		}

		/// <summary>
		/// Pops the domain. Activating previous one removing top.
		/// </summary>
		/// <param name="switchEffect">The switch effect.</param>
		/// <param name="timeToSwitch">The time to switch.</param>
		public void PopScene(ISceneSwitchEffect switchEffect, float timeToSwitch) {
			if (_sceneStack.Count > 0) {
				_sceneStack.Pop().Deactivate();

				if (null != switchEffect) {
					StartEffect(switchEffect, timeToSwitch);
				}

				if (_sceneStack.Count > 0) {
					_sceneStack.Peek().Activate();
				}
			} else {
				throw new SceneGraphException("Unable to pop scene. Scene stack is empty.");
			}
		}

		/// <summary>
		/// Starts the effect.
		/// </summary>
		/// <param name="switchEffect">The switch effect.</param>
		/// <param name="timeToSwitch">The time to switch.</param>
		private void StartEffect(ISceneSwitchEffect switchEffect, float timeToSwitch) {
			if (null == switchEffect) throw new ArgumentNullException("switchEffect");
			if (timeToSwitch < 0.05f) throw new ArgumentException("Scene switch effect should be at least 50 ms length", "timeToSwitch");

			//1. Dump old target data.... into texture....
			AutoTargetTexture currentImage = _primaryTarget;
			_primaryTarget = _backupTarget;
			_backupTarget = currentImage;

			//2. Create player targeted this effect...
			_switchEffectPlayer = new SceneSwitchEffectPlayer(switchEffect, timeToSwitch, _primaryTarget, _backupTarget);
		}

		#endregion

		#region Management of Overlay Scenes

		/// <summary>
		/// Gets the enumeration of overlay scenes on screen in order from bottom to top.
		/// </summary>
		/// <value>The overlay scenes enumeration.</value>
		public IEnumerable<OverlayScene> OverlayScenes {
			get { return _sceneList; }
		}

		/// <summary>
		/// Gets the enumeration of modal scenes on screen in order from bottom to top.
		/// </summary>
		/// <value>The modal scenes enumeration.</value>
		public IEnumerable<OverlayScene> ModalScenes {
			get { return _modalSceneList; }
		}

		/// <summary>
		/// Removes all of overlay scenes from screen.
		/// </summary>
		public void RemoveAllOverlayScenes() {
			foreach (OverlayScene scene in _sceneList.ToArray()) {
				RemoveOverlayScene(scene);
			}

			foreach (OverlayScene scene in _sceneList.ToArray()) {
				RemoveModalScene(scene);
			}
		}

		/// <summary>
		/// Adds the overlay domain to screen. If domain already exists on screen it will be placed at the top of list.
		/// </summary>
		/// <param name="domain">The domain to add.</param>
		public void AddOverlayScene(OverlayScene scene) {
			if (null == scene) throw new ArgumentNullException("scene");

			bool reorder = _sceneList.Remove(scene);
			_sceneList.Add(scene);

			//call activate if it is not a reorder
			if (!reorder) {
				scene.Activate();
			}
		}

		/// <summary>
		/// Removes the overlay domain from screen.
		/// </summary>
		/// <param name="domain">The domain to remove.</param>
		public void RemoveOverlayScene(OverlayScene scene) {
			if (null == scene) throw new ArgumentNullException("scene");
			if (_sceneList.Remove(scene)) {
				scene.Deactivate();
			}
		}

		/// <summary>
		/// Adds the overlay modal domain to screen. It will receive input exclusively. If domain already exists on screen it will be placed at the top of modal list.
		/// </summary>
		/// <param name="domain">The domain to add.</param>
		public void AddModalScene(OverlayScene scene) {
			if (null == scene) throw new ArgumentNullException("scene");

			bool reorder = _modalSceneList.Remove(scene);
			_modalSceneList.Add(scene);

			//call activate if it is not a reorder
			if (!reorder) {
				scene.Activate();
			}
		}

		/// <summary>
		/// Removes the overlay modal domain from screen.
		/// </summary>
		/// <param name="domain">The domain to remove.</param>
		public void RemoveModalScene(OverlayScene scene) {
			if (null == scene) throw new ArgumentNullException("scene");
			if (_modalSceneList.Remove(scene)) {
				scene.Deactivate();
			}
		}

		#endregion


		/// <summary>
		/// Updates entire graph with specified time delta.
		/// </summary>
		/// <param name="timeDelta">The time delta in seconds.</param>
		public void Update(float timeDelta) {
			foreach (Node element in GetVisibleElements()) {
				_graphSnapshot.AddRoot(element);
			}
			_graphSnapshot.Update(timeDelta);
			_graphSnapshot.Clear();

			//update domain switching effect if it exists
			if (null != _switchEffectPlayer) {
				if (!_switchEffectPlayer.Update(timeDelta)) {
					_switchEffectPlayer.Dispose();
					_switchEffectPlayer = null;
				}
			}
		}

		/// <summary>
		/// Draws the entire domain graph on specified canvas.
		/// </summary>
		/// <param name="canvas">The <see cref="Vortex.Drawing.Canvas2D"/> instance used for target drawing.</param>
		public void Draw(Canvas2D canvas) {
			//first, lets draw content of screen into primary target
			DrawContent(_primaryTarget.Canvas);


			//draw domain through effect or some else...
			if (_switchEffectPlayer != null) {
				_switchEffectPlayer.Draw(canvas);
			} else {
				canvas.DrawTexturedRect(canvas.Region, _primaryTarget, _primaryTarget.Region, ColorU.Blank);
			}
		}

		/// <summary>
		/// Draws the domain content.
		/// </summary>
		/// <param name="canvas">The canvas.</param>
		private void DrawContent(Canvas2D canvas) {
			//draw background
			Scene scene = CurrentScene;
			if (null == scene) {
				canvas.Clear(ColorU.Black);
			}

			//draw all of present scenes
			foreach (Node element in GetVisibleElements()) {
				element.DrawContent(canvas);
			}
		}

	}
}
